package com.jacoco.incremental.git;

import com.jacoco.incremental.config.JacocoConfig;
import com.jacoco.incremental.utils.Empty;
import com.jacoco.incremental.utils.ViewInfo;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * git 操作
 * 这里只用来拉去代码，切换分支，pull代码
 */
public class MyGit {

    private final String GIT_FILE_NAME = ".git";
    private final String HEAD_TREE = "^{tree}";
    private final Pattern pattern = Pattern.compile("@@ -\\d+,\\d+ \\+(\\d+),\\d+ @@");
//    private final Pattern diffPatern = Pattern.compile("(diff --git)([\\s\\S]*)(?=(diff --git))|(diff --git)([\\s\\S]*)");

    private Git git = null;

    private JacocoConfig config;


    class Build {
        private Build() {
        }

        public void start() {
            if (config.getJacocoGitUrl().startsWith("http")) {
                findGit();
                try {
                    diffCompareByVersion();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    gitClose();
                }
                return;
            }
            ViewInfo.error("不支持ssh链接！");
        }

        /**
         * 获取git
         */
        private void findGit() {
            File file = new File(config.getJacocoPathSource());
            if (file.exists() && file.isDirectory()) {
//                存在判断是否有.git文件
                File[] files = file.listFiles(new FileFilter() {
                    public boolean accept(File f) {
                        return f.getName().equals(GIT_FILE_NAME) && f.isDirectory();
                    }
                });
                if (files.length > 0) {
                    gitPull(file);
                    return;
                }
            }
//                不存在 clone
            gitClone(file);
        }
    }

    public void build() {
        new Build().start();
    }

    public MyGit setConfig(JacocoConfig config) {
        this.config = config;
        return this;
    }

    private void gitPull(File file) {
        try {
            this.git = Git.open(new File(config.getJacocoPathSource()));
            git.pull().setRemoteBranchName(config.getJacocoGitBranch())
                    .setCredentialsProvider(authentication())
                    .call();
            ViewInfo.info("git pull");
        } catch (Exception e) {
            ViewInfo.error("git pull fail {}", config.getJacocoGitUrl());
            e.printStackTrace();
        }
    }

    /**
     * 获取身份验证
     *
     * @return
     */
    private CredentialsProvider authentication() {
        UsernamePasswordCredentialsProvider user =
                new UsernamePasswordCredentialsProvider(config.getJacocoGitUser(), config.getJacocoGitPswd());
        return user;
    }

    private void gitClone(File file) {
        if (!file.exists() && !file.isDirectory()) file.mkdirs();
        CloneCommand cloneCommand = Git.cloneRepository()
                .setBranch(config.getJacocoGitBranch())
                .setURI(config.getJacocoGitUrl())
                .setDirectory(file)
                .setCredentialsProvider(authentication());
        try {
            ViewInfo.info("git clone");
            this.git = cloneCommand.call();
        } catch (GitAPIException e) {
            ViewInfo.error("git clone fail {}", config.getJacocoGitUrl());
            e.printStackTrace();
        }
    }

    /**
     * 版本差异对比
     */
    private void diffCompareByVersion() throws Exception {
        AbstractTreeIterator oldTreeIter = null;
        AbstractTreeIterator newTreeIter = null;
        if (Empty.isNotEmpty(config.getJacocoGitV1()) && Empty.isNotEmpty(config.getJacocoGitV2())) {

            Repository rep = this.git.getRepository();
            ObjectReader reader = rep.newObjectReader();
            CanonicalTreeParser newTreeIter1 = new CanonicalTreeParser();
            newTreeIter1.reset(reader, rep.resolve(config.getJacocoGitV1() + HEAD_TREE));
            CanonicalTreeParser oldTreeIter2 = new CanonicalTreeParser();
            oldTreeIter2.reset(reader, rep.resolve(config.getJacocoGitV2() + HEAD_TREE));

            newTreeIter = newTreeIter1;
            oldTreeIter = oldTreeIter2;
        } else {
            List<RevCommit> revCommitList = new ArrayList<RevCommit>();
            Iterable<RevCommit> logs = this.git.log().setMaxCount(2).call();

            logs.forEach(revCommit -> {
                revCommitList.add(revCommit);
            });
            if (revCommitList.size() == 2) {
                newTreeIter = prepareTreeParser(revCommitList.get(0));
                oldTreeIter = prepareTreeParser(revCommitList.get(1));
            } else {
                ViewInfo.error("git only one submit");
                throw new Exception("git only one submit,is not need compare");
            }
        }

        List<DiffEntry> diffs = this.git.diff().setNewTree(newTreeIter).setOldTree(oldTreeIter).call();
        diffCompare(diffs);
    }


    /**
     * 收集差异 和 class
     *
     * @param diffEntries 差异对象
     */
    private void diffCompare(List<DiffEntry> diffEntries) throws IOException {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        DiffFormatter formatter = new DiffFormatter(stream);
        //设置比较器为忽略空白字符对比（Ignores all whitespace）
        formatter.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);
        formatter.setRepository(git.getRepository());

        for (DiffEntry entry : diffEntries) {
            if (entry.getNewPath().endsWith(".java")) {
                formatter.format(entry);
            }
        }
        findDiffCoordinates(stream.toString("utf-8"));
    }


    /**
     * 差异坐标点
     */
    private void findDiffCoordinates(String infos) {
        ViewInfo.info(infos);
        Map<String, List<String>> diffInfos = this.config.getDiffInfos();
        List<String> diffClass = new ArrayList<>();
        List<String> diffs = diffInfoSplite(infos);
        for (String diff : diffs) {
            String name = "";
            boolean newFile = false;
            int start = 0;
            int index = 0;
            List<String> coordinates = new ArrayList<String>();

            for (String line : diff.split("\n")) {
                if (!(line.startsWith("diff --git") || line.startsWith("index")
                        || line.startsWith("--- a/"))) {
                    if (line.startsWith("+++ b/")) {
                        if (line.startsWith("+++ b/")) {
                            diffClass.add(name = getDiffClass(line));
                            if (newFile) {
                                ViewInfo.info("new file: {}", name);
                                break;
                            }
                        }
                    } else if (line.startsWith("new file")) {
                        newFile = true;
                    } else if (line.startsWith("@@")) {
                        index = 0;
                        Matcher matcher = pattern.matcher(line);
                        if (matcher.find()) {
                            String group = matcher.group();
                            start = Integer.valueOf(group.substring(group.lastIndexOf("+") + 1, group.lastIndexOf(",")));
                        }
                    } else {
                        if (line.startsWith("+")) {
                            if (line.indexOf("import") > -1) {
                                String newLine = line.substring(1).trim();
                                if (newLine.startsWith("import")) {
                                    index++;
                                    continue;
                                }
                            }
                            coordinates.add((start + index++) + "");
                        } else if (!line.startsWith("-")) {
                            index++;
                        }
                    }
                }
            }
            if (diffInfos.containsKey(name)) {
                diffInfos.get(name).addAll(coordinates);
            } else {
                diffInfos.put(name, coordinates);
            }
        }
        this.config.setClassPath(diffClass);
    }


    /**
     * 将修改信息 按照diff 进行拆分
     *
     * @param infos
     * @return
     */
    private List<String> diffInfoSplite(String infos) {
        List<String> list = new ArrayList<>();

        String[] diffs = infos.split("diff --git a/");
        for (String diff : diffs) {
            if (!diff.equals(""))
                list.add("diff --git a/" + diff);
        }
        return list;
    }

    /**
     * 获取变更后的class 全路径
     *
     * @param line 如：+++ b/项目名称/src/main/java/com/dxy/platform/utils/git/GitUtils.java
     *             取 com/dxy/platform/utils/git/GitUtils.java 作为全路径
     * @return com.dxy.platform.utils.git/GitUtils.java
     */
    private String getDiffClass(String line) {
        String path = "src/main/java/";
        line = line.substring(line.indexOf(path) + path.length());
        String _package = line.substring(0, line.lastIndexOf('/')).replace("/", ".");
        String clazz = line.substring(line.lastIndexOf('/') + 1);
        return _package + "/" + clazz;
    }

    /**
     * 获取TreeIterator
     *
     * @param revCommit
     * @return
     */
    private AbstractTreeIterator prepareTreeParser(RevCommit revCommit) throws IOException {
        CanonicalTreeParser treeParser = new CanonicalTreeParser();
        ObjectReader reader = git.getRepository().newObjectReader();


        RevWalk walk = null;
        try {
            walk = new RevWalk(git.getRepository());
            RevTree tree = walk.parseTree(revCommit.getTree().getId());
            RevTree revTree = walk.parseTree(tree.getId());
            treeParser.reset(reader, revTree.getId());
        } catch (IOException e) {
            throw new IOException(e.getMessage());
        } finally {
            walk.dispose();
        }

        return treeParser;
    }

    private void gitClose() {
        this.git.close();
    }
}
